package redisDB

import (
	"bytes"
	"context"
	"encoding/json"
	"strconv"
	"strings"
	"time"

	"github.com/go-redis/redis/v8"
)

//ReidsRepository 仓储(string)
type ReidsRepository struct {
	client     redis.Cmdable
	entityName string
	timeout    time.Duration //超时时间(秒)
}

//getKey 获取key
func (r *ReidsRepository) getKey(key string) string {
	buf := strings.Builder{}
	buf.WriteString(r.entityName)
	buf.WriteString(":")
	buf.WriteString(key)
	return buf.String()
}

//getHKey 获取Incr递增字段特殊key
func (r *ReidsRepository) getHKey(key string) string {
	return r.CreateKey("Incr",key)
}

//CreateKey 创建内部key
func (r *ReidsRepository) CreateKey(prefix,key string) string  {
	buf := strings.Builder{}
	buf.WriteString(prefix)
	buf.WriteString(":")
	buf.WriteString(r.entityName)
	buf.WriteString(":")
	buf.WriteString(key)
	return buf.String()
}


//Set 插入单个[key数据存在会更新]
// entity  	   结构体
// ttl     单位秒 ，0为永不到期
func (r *ReidsRepository) Set(key string, entity interface{}, ttl int64) (int64, error) {
	keyStr := r.getKey(key)
	//json.Marshal()
	//序列化
	val, err := json.Marshal(entity)
	if err != nil {
		return 0, err
	}
	ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
	defer cancel()

	_, err = r.client.Set(ctx,keyStr, string(val), time.Second * time.Duration(ttl)).Result()
	if err != nil {
		return 0, err
	}
	return 1, nil
}

//SetMany 插入多个    [key数据存在会更新]
// entityMap   结构体字典
func (r *ReidsRepository) SetMany(entityMap map[string]interface{}) (int64, error) {

	ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
	defer cancel()

	pipe := r.client.Pipeline()
	for key, val := range entityMap {
		valBt, err := json.Marshal(val)
		if err != nil {
			return 0, err
		}
		pipe.Set(ctx,r.getKey(key), string(valBt), 0)
	}
	cmders, err := pipe.Exec(ctx)
	if err != nil {
		return 0, err
	}
	return int64(len(cmders)), nil
}

//Incr 递增
func (r *ReidsRepository) Incr(key ,field string,num int64) (int64,error) {
	ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
	defer cancel()

	keyStr := r.getHKey(key)
	return r.client.HIncrBy(ctx,keyStr, field, num).Result()
}

//RemoveIncr 清理递增字段
func (r *ReidsRepository) RemoveIncr(key string) error {
	ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
	defer cancel()

	keyStr := r.getHKey(key)
	_, err := r.client.Del(ctx,keyStr).Result()
	if err != nil {
		return err
	}
	return nil
}

//GetIncr 获取递增值
func (r *ReidsRepository) GetIncr(key string,res map[string]int64) (err error) {
	ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
	defer cancel()

	keyStr := r.getHKey(key)
	result, err := r.client.HGetAll(ctx,keyStr).Result()
	if err != nil {
		return err
	}
	for key, _ := range res {
		value, ok := result[key]
		if !ok {
			continue
		}
		parseInt, err := strconv.ParseInt(value, 10, 64)
		if err != nil {
			continue
		}
		res[key] = parseInt
	}
	return nil
}

//Remove 移除
// keys key值切片
func (r *ReidsRepository) Remove(keys ...string) (int64, error) {
	ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
	defer cancel()

	keySlice := make([]string, 0, 1+len(keys))
	for _, val := range keys {
		keySlice = append(keySlice, r.getKey(val))
	}
	count, err := r.client.Del(ctx,keySlice...).Result()
	if err != nil {
		return 0, err
	}
	return count, nil
}

//Get 查询单个
// key         key值
// result      查询的结果
func (r *ReidsRepository) Get(key string, result interface{}) error {
	ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
	defer cancel()

	keyStr := r.getKey(key)
	value, err := r.client.Get(ctx,keyStr).Result()
	if err != nil {
		return err
	}
	err = json.Unmarshal([]byte(value), result)
	if err != nil{
		return err
	}
	return nil
}

//GetMany 查询多个
// keys        key值切片
// result      查询的结果切片
func (r *ReidsRepository) GetMany(keys []string, result interface{}) error {
	ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
	defer cancel()

	pipe := r.client.Pipeline()
	defer pipe.Close()

	for _, val := range keys {
		pipe.Get(ctx,r.getKey(val))
	}
	cmders, err := pipe.Exec(ctx)
	if err != nil && len(cmders) == 0 {
		return err
	}
	var buf = bytes.Buffer{}
	buf.WriteString("[")
	var count = len(cmders)
	for i := 0; i < count; i++ {
		cmd := cmders[i].(*redis.StringCmd)
		strVal, err := cmd.Result()
		if err != nil {
			buf.WriteString("{}")
		} else {
			buf.WriteString(strVal)
		}
		if i < count-1 {
			buf.WriteString(",")
		}
	}
	buf.WriteString("]")

	err = json.Unmarshal(buf.Bytes(), result)
	if err != nil{
		return err
	}
	return nil
}

//Do 直接执行
func (r *ReidsRepository) Do(h func(redis.Cmdable))  {
	h(r.client)
}
